package com.puzzlebazar.client.squaregrid.model;

import java.io.Serializable;
import java.util.Arrays;

import com.puzzlebazar.client.puzzle.model.Move;
import com.puzzlebazar.client.puzzle.model.MoveImpl;
import com.puzzlebazar.client.puzzle.model.MoveList;
import com.puzzlebazar.client.util.BoundInfo;
import com.puzzlebazar.client.util.GridValidityImpl;
import com.puzzlebazar.client.util.Recti;
import com.puzzlebazar.client.util.Vec2i;

public class StateGridImpl implements StateGrid, Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 196908822132341683L;
	
	private int defaultState;
	private int nbStates;

	private Vec2i gridSize;
	private int states[][] = null;
	
	/**
	 * Provided only fot GWT serialization
	 */
	@SuppressWarnings("unused")
	private StateGridImpl() {
		this.gridSize = null;
		this.defaultState = 0;
		this.nbStates = 0;
		this.states = null;
	}
	
	/**
	 * Create a new squaregrid class
	 * 
	 * @param gridSize The initial size of that square grid
	 */
	public StateGridImpl( Vec2i gridSize, int defaultState, int nbStates ) {
		this.gridSize = new Vec2i( gridSize );
		this.defaultState = defaultState;
		this.nbStates = nbStates;
		clearStates();
	}
	
	@Override
	public Vec2i getGridSize() {
		return gridSize;
	}

	@Override
	public Move clearStates( long time ) {
		clearStates();		
		return new MoveClearStates( time );
	}

	/**
	 * Clear all the states, do not generate a move
	 */
	private void clearStates() {
		if( gridSize == null || gridSize.x < 0 || gridSize.y < 0 )
			states = null;
		else {
			states = new int[gridSize.x][gridSize.y];
			for (int i = 0; i < states.length; i++) {
				Arrays.fill( states[i], defaultState );
			}
		}
	}

	public int getState(Vec2i loc) throws Exception {
		if( !isValidCell( loc ) )
			throw new Exception( "Invalid cell location" );
		return states[loc.x][loc.y];		
	}

	@Override
	public Move setState(Vec2i loc, int stateAfter, long time) throws Exception {
		if( !isValidCell( loc ) )
			throw new Exception( "Invalid cell location" );

		Move move = new MoveChangeState(loc, states[loc.x][loc.y], stateAfter, time);

		states[loc.x][loc.y] = stateAfter;

		return move;
	}

	/**
	 * Append the moves corresponding to the states
	 * 
	 * @param moveList The move list to append to
	 */
	public void appendMoves(MoveList moveList) {
		if( states == null ) return;
		int defaultState = getDefaultState();
		for (int i = 0; i < states.length; i++) {
			for (int j = 0; j < states[i].length; j++) {
				if( states[i][j] != defaultState ) {
					moveList.addMove( new MoveChangeState(new Vec2i(i,j),defaultState,states[i][j],MoveImpl.NO_TIME) );
				}
			}
		}
	}	

	/**
	 * Checks if the states of this model matches those of the other model
	 * 
	 * @param other Model to check against
	 * @param equivalenceClasses A vector containing an integer for every possible
	 *                           state. If two integers are the same, the states are 
	 *                           equivalent. Can be null if all states are different.
	 * @return
	 */
	public boolean match( StateGridImpl other, int equivalenceClasses[] ) {
		
		if( states.length != other.states.length ) return false;

		if( equivalenceClasses == null ) {
			equivalenceClasses = new int[ getNbStates() ];
			for (int i = 0; i < equivalenceClasses.length; i++)
				equivalenceClasses[i] = i;
		}
		
		for (int i = 0; i < states.length; i++) {
			if( states[i].length != other.states[i].length ) return false;
			for (int j = 0; j < states[i].length; j++) {
				if( equivalenceClasses[ states[i][j] ] != 
					equivalenceClasses[ other.states[i][j] ] ) return false;
			}
		}

		return true;	
	}

	@Override
	public boolean isValidCell(Vec2i loc) {
		return GridValidityImpl.isValidCell(loc, gridSize);
	}

	@Override
	public boolean isValidVertex(Vec2i loc) {
		return GridValidityImpl.isValidVertex(loc, gridSize);
	}	

	@Override
	public boolean isValidVerticalEdge(Vec2i loc) {
		return GridValidityImpl.isValidVerticalEdge(loc, gridSize);
	}	

	@Override
	public boolean isValidHorizontalEdge(Vec2i loc) {
		return GridValidityImpl.isValidHorizontalEdge(loc, gridSize);
	}

	@Override
	public boolean isValidRect(Recti rect) {
		return GridValidityImpl.isValidRect(rect, gridSize);
	}

	@Override
	public int getDefaultState() {
		return defaultState;
	}

	@Override
	public int getNbStates() {
		return nbStates;
	}

	@Override
	public Move expandBottom(int delta) throws Exception {
		return resize( new Vec2i( gridSize.x, gridSize.y + delta ) );
	}

	@Override
	public Move expandLeft(int delta) throws Exception {
		return resize( new Vec2i( gridSize.x + delta, gridSize.y ) );
	}

	@Override
	public Move expandRight(int delta) throws Exception {
		return resize( new Vec2i( gridSize.x + delta, gridSize.y ) );
	}

	@Override
	public Move expandTop(int delta) throws Exception {
		return resize( new Vec2i( gridSize.x, gridSize.y + delta ) );
	}

	@Override
	public Move resize(Vec2i gridSize) throws Exception {
		this.gridSize.copy( gridSize );
		// TODO This should be a resize move, probably with the time of the resize
		return clearStates( MoveImpl.NO_TIME );
	}

	@Override
	public BoundInfo getBoundInfo() {
		return null;
	}

}
